package dslab.mailbox;

import dslab.core.DMTPHandler;
import dslab.core.DMTPProtocol;
import dslab.util.*;

import java.io.*;
import java.net.Socket;
import java.util.*;
import java.util.concurrent.BlockingQueue;

/**
 * mailbox related Handler class that is runnable and should run in its own thread;
 * implements the DMTPProtocol, which forces it to implement all methods that are required to deal with DMTP;
 * re-uses a great deal of code from DMTPHandler
 */
public class MailboxDMTPHandler extends DMTPHandler implements Runnable, DMTPProtocol {

    private final String domain;
    private final Config users;
    private final BlockingQueue<WriteTask> writeTasks;
    private final List<String> targetUsers;
    private final Map<String, List<Message>> mails;

    /**
     * creates a new MailboxDMTPHandler client connection handler
     * @param componentId string representation of component
     * @param socket client connection socket that was already accepted elsewhere
     * @param domain mailbox's own domain
     * @param users configuration of users used for look-ups
     * @param writeTasks queue of Messages to push new write tasks to
     * @param mails reference to map with mapping of users to list of messages
     * @throws IOException exception if socket cannot be configured
     */
    public MailboxDMTPHandler(String componentId, Socket socket, String domain, Config users,
                              BlockingQueue<WriteTask> writeTasks,
                              Map<String, List<Message>> mails) throws IOException {
        super(componentId, socket);
        this.domain = domain;
        this.users = users;
        this.writeTasks = writeTasks;
        this.targetUsers = new ArrayList<>();
        this.mails = mails;
    }

    @Override
    public void handleTo(Writer out, List<String> addresses, String addressesString) throws IOException {
        if (isNotInState(out, SessionState.EditingMessage, ILLEGAL_STATE_WARNING))
            return;

        // new to requires a cleared list
        targetUsers.clear();

        // validate each address (what is not already validated)
        // and count how many users are part of this domain
        for (String address : addresses) {
            String[] split = address.split("@");
            String user = split[0];
            String mailbox = split[1];

            // only consider relevant users
            if (!mailbox.equals(domain))
                continue;

            // validate users
            if (!users.containsKey(user)) {
                // exit preemptively in failure case
                raiseWarning(out, String.format("error unknown recipient %s%n", user));
                return;
            }

            // count same-domain recipients and keep track of relevant recipients
            targetUsers.add(user);
        }

        // having recipients where no recipient has the domain of this mailbox
        // should be impossible, as such it is considered an attack and therefore
        // a failure case
        if (targetUsers.size() == 0) {
            raiseWarning(out, String.format("error no recipient is part of this mailbox domain%n"));
            return;
        }

        // communicate result
        out.write(String.format("ok %d%n", targetUsers.size()));
        out.flush();
        message.recipients = addressesString;
    }

    @Override
    public void handleFrom(Writer out, String address) throws IOException {
        if (isNotInState(out, SessionState.EditingMessage, ILLEGAL_STATE_WARNING))
            return;

        // no domain validation, as mailbox does not forward
        // this means messages can have spoofed senders

        // communicate result
        out.write(String.format("ok%n"));
        out.flush();
        message.sender = address;
    }

    @Override
    public void handleSend(Writer out) throws IOException, InterruptedException {
        if (isNotInState(out, SessionState.EditingMessage, ILLEGAL_STATE_WARNING))
            return;

        // can't send until message is complete
        if (!message.isMessageIncomplete()) {
            raiseWarning(out, String.format("error %s%n", message.getMissingFields()));
            return;
        }

        // put message to blocking queue that handles write tasks (in a separate thread)
        // this model adheres to the single writer principle
        // add message task will create deep copies (for concurrency reasons)
        writeTasks.put(
            new AddMessageTask(
                String.format("%s-add-message-task", componentId),
                message,
                targetUsers,
                mails
            )
        );

        // communicate result
        out.write(String.format("ok%n"));
        out.flush();
        state = SessionState.NoMessages;
        message.reset();
        targetUsers.clear();
    }
}
